package com.subumbo.audio.core
{
	import __AS3__.vec.Vector;
	
	import com.subumbo.audio.loader.AudioLoader;
	import com.subumbo.audio.processing.LowPass2pole;
	
	import flash.display.Shader;
	import flash.display.ShaderJob;
	import flash.events.Event;
	import flash.events.SampleDataEvent;
	import flash.media.Sound;
	import flash.media.SoundChannel;
	import flash.utils.ByteArray;
	
	import mx.core.Application;
	
	public class AudioEngine
	{
		private static var _instance:AudioEngine;
		
		/* [Embed(source="assets/pixelbender/sawtooth.pbj", mimeType="application/octet-stream")]
		private var sawtoothGenerator:Class;
		private var sawGen:Shader = new Shader(new sawtoothGenerator() as ByteArray);
		 */
		//pixelbender audio mixer
		[Embed(source="15ChannelMixer.pbj", mimeType="application/octet-stream")]
		private var Mixer:Class;
		private var _mixer:Shader = new Shader(new Mixer() as ByteArray); 
		
		/**
		 * Sequecner members
		 * */
		private var dt:int;
		private var _lastTime:int = 0;
		private var _currentPosition:uint;
		private var _midiChannels:Array;
		private var _count:uint = 1;
		private var _lastTime2:int = 0;
		private var _time:int = 0;
		private var _sampleTime:Number;
		
		
		/**
		 * DSP public members
		 * */
		[Bindable]
		public var selectedChannel:uint;
		private var _endPositions:Vector.<uint>;
		private var _startPositions:Vector.<uint>;
		private var _loop:Array = [];
		private var _mainCutoff:Number = 2000;
		private var _mainRes:Number = 50;		
		
		/**
		 * DSP private members
		 * */
		private var BUFFER_SIZE:uint = 2048;
		private var BUFFER_SIZE_BYTES:uint = 8 * BUFFER_SIZE;
		private var _dsp:Sound = new Sound();
		private var _dspChannel:SoundChannel;
		private var _silence:ByteArray;
		
		private var _voices:Vector.<Boolean>;
		private var _audioChannels:Vector.<ByteArray>;
		private var _volumes:Vector.<Number>;
		
		private var _pointers:Vector.<uint>;
		private var _maxLength:Vector.<uint>;
		
		private var _masterBuffer:ByteArray = new ByteArray();
				
		private var _lp:LowPass2pole = new LowPass2pole();
		//private var _reverb:Freeverb;
		
		private var _recCount:uint = 0;
		private var _bouncing:Boolean;
		/**
		 * Data members
		 * */
		private var _ldr:AudioLoader;
		private var _sounds:Vector.<Sound>;
		private var _soundData:Vector.<ByteArray>;
		private var _track:ByteArray = new ByteArray();
		
		/**
		 * Flags
		 * */
		 private var _run:Boolean;
		 private var _sequencerReady:Boolean;
		///
		///
		
		public static function get Instance():AudioEngine
		{
			if(_instance == null) _instance = new AudioEngine();
			return _instance;
		}
		
		public function AudioEngine()
		{
			if(_instance != null) throw new Error("Singleton");
		}
		
		public function startup(loader:AudioLoader):void
		{
			_ldr = loader;
			
			if(_ldr.loaded) _soundData = _ldr.getBytes();
			else {}
			
			_maxLength = new Vector.<uint>(8);
			_sounds = _ldr.getSounds();
			for(var i:uint = 0; i < _ldr.getSounds().length; i++)
			{
				var sound:Sound = _sounds[i];
				var l:Number = Math.floor(sound.length * 44.1 *8)
				_maxLength[i] = l /1.5 ;
			}
			
			initDsp();
		}
		
		
		private function initSequencer():void
		{
			_currentPosition = 0;
			
			_midiChannels = []
			for(var i:uint = 0; i < 9; i++)
			{
				var midiChannel:Vector.<MidiObject> = new Vector.<MidiObject>();
				for(var j:uint = 0; j < 16; j++)
				{
					var midi:MidiObject = new MidiObject();
					midiChannel.push(midi);
				}
				_midiChannels.push(midiChannel);
				_loop.push(false);
			}
		}
		
		private function onLoadComplete(e:Event):void
		{
			_soundData = new Vector.<ByteArray>();
			for(var j:uint = 0; j < _sounds.length; j++)
				_soundData.push(new ByteArray());
			
			_maxLength = new Vector.<uint>(8);
			for(var i:uint = 0; i < _sounds.length; i++)
			{
				var sound:Sound = _sounds[i];
				var l:Number = Math.floor(sound.length * 44.1 *8)
				sound.extract(_soundData[i], l);
				_maxLength[i] = l / 2;
			}
			
			for(var n:uint = 0; n < _soundData.length; n++)
				_soundData[n].position = 160;	
		}
		
		private function advancePlayhead():void
		{	
			var j:uint = 0;
			for(j; j < _midiChannels.length; j++)
			{
				switch(_midiChannels[j][_currentPosition].note)
				{
					case 1 : 	_voices[j] = true; _pointers[j] = 0;break;
					case 2 :   	_voices[j] = false; break; 
				}
			}
		
			if(_currentPosition == 15)
			{
				_currentPosition = 0;
			}else	
			{
				_currentPosition++;
			}
			Application.application.sequencer.advancePlayhead();
			
		}
		
		public function changePattern(channel:uint, index:uint):void
		{
			var boo:uint = _midiChannels[channel][index].note;
			switch(boo)
			{
				case 0 : 	_midiChannels[channel][index].note = 1; break;	
				case 1 : 	_midiChannels[channel][index].note = 2; break;
				case 2 : 	_midiChannels[channel][index].note = 0; break;		
			}
		}
		
		public function changeVolume(channel:uint, val:Number):void
		{
			channel--;
			_volumes[channel] = val;
		}
		
		
		private function initDsp():void
		{	
			var i:uint = 0;
			_silence = new ByteArray();
			for(i; i < BUFFER_SIZE * 2; i++)
				_silence.writeFloat(0x00000000);
			_silence.position = 0;
			
			_voices = new Vector.<Boolean>(8);
			for(i = 0; i < 9; i++)
			{
				_voices[i] = false;
			}
			
			_pointers = new Vector.<uint>(8);
			_endPositions = new Vector.<uint>(8);
			_startPositions = new Vector.<uint>(8);
			
			for(i = 0; i < _soundData.length; i++)
			{
				_pointers[i] = 0;				
				_endPositions[i] = _maxLength[i];
			}
			
			_audioChannels = new Vector.<ByteArray>(15);
			_volumes = new Vector.<Number>(15);
			for (var j:int = 0; j < 15; j++) 
			{
				_volumes[j] = 0.8;
  				_audioChannels[j] = new ByteArray();
 				 // set so shader will always work even we have not enough tracks
 				 _audioChannels[j].length = BUFFER_SIZE*4*2; 
				 _mixer.data["track"+j]["width"] = 1024;
				 _mixer.data["track"+j]["height"] = BUFFER_SIZE/1024;
				 _mixer.data["track"+j]["input"] = _audioChannels[j];
				 _mixer.data["volume"+j]["value"] = [ _volumes[j], _volumes[j] ];
			
			}
			_voices[0] = true;
			_loop[0] = false;	
			masterResonance = 0;	
			play();	
		}
		
		private function callBack(e:SampleDataEvent):void
		{
			
			/**
			 * 
			 * PROCESS ---------------------------------- PROCESS
			 * 
			 * */
			
			var i:uint = 0;
			
			for(i;i < _audioChannels.length; i++) 		// reset mixerchannel buffers
				_audioChannels[i].position = 0;
			 
			 /**
			 * 
			 * 
			 * Sampler
			 * 
			 * 
			 * */
			
			i = 0;
			for(i; i < 8; i++)
			{	
				if(_pointers[i]*BUFFER_SIZE_BYTES >= _endPositions[i])
				{
					if(_loop[i])	_pointers[i] = 0;
					else			_voices[i] = false;
				}
		
				if(_voices[i])
				{
					var pos:uint = _pointers[i] * BUFFER_SIZE_BYTES;
					_audioChannels[i].writeBytes(_soundData[i], _pointers[i] * BUFFER_SIZE_BYTES + _startPositions[i], BUFFER_SIZE_BYTES);
					_pointers[i]++;
					
				}else
				{
					_audioChannels[i].position = 0;
					_audioChannels[i].writeBytes(_silence)
				}
					
			}
			  	
			/**
			 * 
			 * 
			 * MIXING
			 * 
			 * 
			 * */
			 
			/* for (var k:int = 0; k < 8; k++)
    			_mixer.data["volume"+k]["value"] = [ _volumes[k], _volumes[k] ];
			 */
			var mixerJob:ShaderJob = new ShaderJob(_mixer, _masterBuffer, 1024, BUFFER_SIZE/1024);
				  mixerJob.start(true);
			
			/**
			 * 
			 * 
			 * MAIN FILTER	
			 * 
			 * 
			 * */	
			 
			_masterBuffer.position = 0;
			var master:ByteArray = new ByteArray();
	
			
			/* i = 0;
			for(i; i < BUFFER_SIZE; i++)
			{
				master.writeFloat(_lp.filterSample(_masterBuffer.readFloat(), _mainCutoff,_mainRes));
				master.writeFloat(_lp.filterSample(_masterBuffer.readFloat(), _mainCutoff,_mainRes))
			}*/
			_masterBuffer.position = 0;
			master.position = 0; 
			
			
			if(_bouncing)
			{
				var l:uint = _recCount * BUFFER_SIZE_BYTES;
				master.readBytes(_track, l);
				_recCount++;
			}else	e.data.writeBytes(_masterBuffer);		 
		}
		
		public function bounce():void
		{
			stop();
			_track = new ByteArray();
			_bouncing = true;
			_currentPosition = 0;
			var i:uint = 0;
			for(i; i < 50; i++)
			{
				callBack(new SampleDataEvent(SampleDataEvent.SAMPLE_DATA));		
			}
			_bouncing = false;
			_currentPosition = 0;
		}
		
		public function playRecorded():void
		{
			stop();
			_soundData[0] = _track;
			_recCount = 0;
			//var _player:Sound = new Sound();
			//_player.addEventListener(SampleDataEvent.SAMPLE_DATA, onPlayBack);
			//_player.play();
		}
		
		private function onPlayBack(e:SampleDataEvent):void
		{
			e.data.writeBytes(_track);
			_recCount++;
		}
		
		public function noteOn(voice:uint):void
		{
			_voices[voice] = 1;
			_pointers[voice] = 0;
		}
		
		public function noteOff(voice:uint):void
		{
			_voices[voice] = 2;
			_pointers[voice] = 0;
		}
		
		public function toggle(channel:int):void
		{
			_voices[channel] = true;
			_pointers[channel] = 0;
		}
		
		public function play():void
		{
			_dsp.addEventListener(SampleDataEvent.SAMPLE_DATA, callBack);
			_dspChannel = _dsp.play();
		}
		
		public function stop():void
		{
			if(_dspChannel)
			{
				_dsp.removeEventListener(SampleDataEvent.SAMPLE_DATA, callBack);
				_dspChannel.stop();
			}
		}
		
		public function shutDown():void
		{
			
		}
		
		public function get midiChannels():Array { return _midiChannels; }
		
		public function get currentPosition():uint { return _currentPosition; }
		
		public function get volumes():Vector.<Number> { return _volumes; }
		
		public function set loop(val:Array):void { _loop = val; }
		public function get loop():Array { return _loop; }
		
		public function set masterResonance(val:Number):void { _mainRes = val; }
		public function set masterCutoff(val:Number):void { _mainCutoff = val; }
		
		
		public function setStartPosition(percentage:Number):void
		{
			var n: uint = new uint(_maxLength[selectedChannel] / 100 * percentage);
			if(n % 4 == 0)
			{
				_startPositions[selectedChannel] = n;	
			}	
		}
		
		public function setEndPosition(percentage:Number):void
		{
			var n: uint = new uint(_maxLength[selectedChannel] / 100 * percentage);
			if(n % 4 == 0)
			{
				_endPositions[selectedChannel] = n;	
			}	
		}
		
		// Observer
		private var _listeners:Array = [];
		
		public function addListener(obj:*):void
		{
			_listeners.push(obj);
		}
		
		public function changeChannel(channel:uint):void
		{
			selectedChannel = channel;
			for each(var l:* in _listeners)
				l.selectionChange();
		} 

	}
}
